package com.smartandroid.sa.drag;

import java.util.ArrayList;

import android.content.Context;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;

/**
 * A subclass of {@link android.widget.CursorAdapter} that provides reordering
 * of the elements in the Cursor based on completed drag-sort operations. The
 * reordering is a simple mapping of list positions into Cursor positions (the
 * Cursor is unchanged). To persist changes made by drag-sorts, one can retrieve
 * the mapping with the {@link #getCursorPositions()} method, which returns the
 * reordered list of Cursor positions.
 * 
 * An instance of this class is passed to
 * {@link DragListView#setAdapter(ListAdapter)} and, since this class
 * implements the {@link DragListView.DragSortListener} interface, it is
 * automatically set as the DragSortListener for the DragSortListView instance.
 */
public abstract class DragSortCursorAdapter extends CursorAdapter implements
		DragListView.DragSortListener {

	public static final int REMOVED = -1;

	/**
	 * Key is ListView position, value is Cursor position
	 */
	private SparseIntArray mListMapping = new SparseIntArray();

	private ArrayList<Integer> mRemovedCursorPositions = new ArrayList<Integer>();

	public DragSortCursorAdapter(Context context, Cursor c) {
		super(context, c);
	}

	public DragSortCursorAdapter(Context context, Cursor c, boolean autoRequery) {
		super(context, c, autoRequery);
	}

	public DragSortCursorAdapter(Context context, Cursor c, int flags) {
		super(context, c, flags);
	}

	/**
	 * Swaps Cursor and clears list-Cursor mapping.
	 * 
	 * @see android.widget.CursorAdapter#swapCursor(android.database.Cursor)
	 */
	@Override
	public Cursor swapCursor(Cursor newCursor) {
		Cursor old = super.swapCursor(newCursor);
		resetMappings();
		return old;
	}

	/**
	 * Changes Cursor and clears list-Cursor mapping.
	 * 
	 * @see android.widget.CursorAdapter#changeCursor(android.database.Cursor)
	 */
	@Override
	public void changeCursor(Cursor cursor) {
		super.changeCursor(cursor);
		resetMappings();
	}

	/**
	 * Resets list-cursor mapping.
	 */
	public void reset() {
		resetMappings();
		notifyDataSetChanged();
	}

	private void resetMappings() {
		mListMapping.clear();
		mRemovedCursorPositions.clear();
	}

	@Override
	public Object getItem(int position) {
		return super.getItem(mListMapping.get(position, position));
	}

	@Override
	public long getItemId(int position) {
		return super.getItemId(mListMapping.get(position, position));
	}

	@Override
	public View getDropDownView(int position, View convertView, ViewGroup parent) {
		return super.getDropDownView(mListMapping.get(position, position),
				convertView, parent);
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		return super.getView(mListMapping.get(position, position), convertView,
				parent);
	}

	/**
	 * On drop, this updates the mapping between Cursor positions and ListView
	 * positions. The Cursor is unchanged. Retrieve the current mapping with
	 * {@link getCursorPositions()}.
	 * 
	 * @see DragListView.DropListener#drop(int, int)
	 */
	@Override
	public void drop(int from, int to) {
		if (from != to) {
			int cursorFrom = mListMapping.get(from, from);

			if (from > to) {
				for (int i = from; i > to; --i) {
					mListMapping.put(i, mListMapping.get(i - 1, i - 1));
				}
			} else {
				for (int i = from; i < to; ++i) {
					mListMapping.put(i, mListMapping.get(i + 1, i + 1));
				}
			}
			mListMapping.put(to, cursorFrom);

			cleanMapping();
			notifyDataSetChanged();
		}
	}

	/**
	 * On remove, this updates the mapping between Cursor positions and ListView
	 * positions. The Cursor is unchanged. Retrieve the current mapping with
	 * {@link getCursorPositions()}.
	 * 
	 * @see DragListView.RemoveListener#remove(int)
	 */
	@Override
	public void remove(int which) {
		int cursorPos = mListMapping.get(which, which);
		if (!mRemovedCursorPositions.contains(cursorPos)) {
			mRemovedCursorPositions.add(cursorPos);
		}

		int newCount = getCount();
		for (int i = which; i < newCount; ++i) {
			mListMapping.put(i, mListMapping.get(i + 1, i + 1));
		}

		mListMapping.delete(newCount);

		cleanMapping();
		notifyDataSetChanged();
	}

	/**
	 * Does nothing. Just completes DragSortListener interface.
	 */
	@Override
	public void drag(int from, int to) {
		// do nothing
	}

	/**
	 * Remove unnecessary mappings from sparse array.
	 */
	private void cleanMapping() {
		ArrayList<Integer> toRemove = new ArrayList<Integer>();

		int size = mListMapping.size();
		for (int i = 0; i < size; ++i) {
			if (mListMapping.keyAt(i) == mListMapping.valueAt(i)) {
				toRemove.add(mListMapping.keyAt(i));
			}
		}

		size = toRemove.size();
		for (int i = 0; i < size; ++i) {
			mListMapping.delete(toRemove.get(i));
		}
	}

	@Override
	public int getCount() {
		return super.getCount() - mRemovedCursorPositions.size();
	}

	/**
	 * Get the Cursor position mapped to by the provided list position (given
	 * all previously handled drag-sort operations).
	 * 
	 * @param position
	 *            List position
	 * 
	 * @return The mapped-to Cursor position
	 */
	public int getCursorPosition(int position) {
		return mListMapping.get(position, position);
	}

	/**
	 * Get the current order of Cursor positions presented by the list.
	 */
	public ArrayList<Integer> getCursorPositions() {
		ArrayList<Integer> result = new ArrayList<Integer>();

		for (int i = 0; i < getCount(); ++i) {
			result.add(mListMapping.get(i, i));
		}

		return result;
	}

	/**
	 * Get the list position mapped to by the provided Cursor position. If the
	 * provided Cursor position has been removed by a drag-sort, this returns
	 * {@link #REMOVED}.
	 * 
	 * @param cursorPosition
	 *            A Cursor position
	 * @return The mapped-to list position or REMOVED
	 */
	public int getListPosition(int cursorPosition) {
		if (mRemovedCursorPositions.contains(cursorPosition)) {
			return REMOVED;
		}

		int index = mListMapping.indexOfValue(cursorPosition);
		if (index < 0) {
			return cursorPosition;
		} else {
			return mListMapping.keyAt(index);
		}
	}

}
